package com.example.miaoshademo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

import static com.example.miaoshademo.Constants.*;

/**
 * @author Shenluw
 * created：2019/7/17 21:46
 */
@Service
public class MemoryMiaoshaServiceImpl implements MiaoshaService {
	private final static Logger log = LoggerFactory.getLogger(MemoryMiaoshaServiceImpl.class);

	@Autowired
	private MiaoshaDataRepository miaoshaDataRepository;

	@Autowired
	private MiaoshaCache miaoshaCache;

	@Autowired
	private MiaoshaOrderRepository miaoshaOrderRepository;

	@Autowired
	private GoodsRepository goodsRepository;

	private final AtomicInteger idGen = new AtomicInteger(0);

	private final ReentrantLock lock = new ReentrantLock();

	private final ReentrantLock orderLock = new ReentrantLock();

	@Override
	public MiaoshaData create(Integer goodsId, Integer stock, Date startTime) {
		MiaoshaData data = new MiaoshaData();
		data
				.setId(idGen.incrementAndGet())
				.setGoodsId(goodsId)
				.setStock(stock)
				.setConsumeStock(0)
				.setCreateTime(new Date())
				.setStartTime(startTime);

		miaoshaDataRepository.save(data);

		miaoshaCache.save(data);

		return data;
	}

	@Override
	public int miaosh(Integer miaoshaId, Integer uid) {
		long millis = System.currentTimeMillis();
		lock.lock();
		try {
			MiaoshaData data = miaoshaCache.get(miaoshaId);
			if (data == null) return MIAOSHA_NOT_FOUND;
			if (data.getStartTime().getTime() > millis) {
				return MIAOSHA_NOT_START;
			}

			Integer stock = data.getStock();

			Integer consumeStock = data.getConsumeStock();
			if (consumeStock == null) {
				consumeStock = 0;
			}
			if (consumeStock >= stock) {
				// 秒杀已经结束
				return MIAOSHA_OUT_OF_STOCK;
			}
			// 秒杀成功
			data.setConsumeStock(consumeStock + 1);
			miaoshaCache.update(data);
			return MIAOSHA_SUCCESS;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public MiaoshaData query(Integer miaoshaId) {
		return miaoshaCache.get(miaoshaId);
	}

	@Transactional
	@Override
	public MiaoshaOrder createOrder(Integer miaoshaId, Integer uid) {
		// 进入这里的并发已经很低了  所以不太需要考虑太多性能问题
		// 可以在这里发送jms 消息来处理订单后续操作

		MiaoshaData data = miaoshaCache.get(miaoshaId);
		if (data == null) {
			// 在一个非常尴尬的时间点，数据被移除了
			// 比如秒杀活动结束时清除了缓存
			// 或者 redis 这些存储中设置了过期导致数据丢失
			// 遇到这些情况可以通过其他补偿方式避免问题
			// 因为这个方法的调用发生在秒杀判定后，间隔比较短
			// 所以只需要设置合理的过期时间或者移除时机就可以避免
			log.info("缓存不见了");
			return null;
		}

		try {
			// 如果使用mq这些中间件此处就可以不加锁，当发生写入冲突时可以通过一些补偿手段重试
			if (orderLock.tryLock() || orderLock.tryLock(1, TimeUnit.SECONDS)) {
				// 这里直接处理订单的简单逻辑
				// 因为开个mq服务太麻烦了

				try {
					MiaoshaOrder order = new MiaoshaOrder();
					order.setGoodsId(data.getGoodsId())
							.setMiaoshaId(miaoshaId)
							.setOrderId(UUID.randomUUID().toString());

					miaoshaOrderRepository.save(order);

					// 减库存操作
					// 由于这里直接进行类库存操作，导致可能发生数据冲突
					Goods goods = goodsRepository.findById(data.getGoodsId()).orElse(null);
					if (goods == null) {
						log.info("商品被删了， 我还能说什么...");
						return null;
					}
					Integer stock     = goods.getStock();
					Integer lockStock = goods.getLockStock();

					if (stock > 0 && lockStock > 0) {
						goods.setStock(stock - 1);
						goods.setLockStock(lockStock - 1);

						goodsRepository.save(goods);
					} else {
						log.info("库存居然会对不上号，锁没加好才会发生");
					}
					return order;
				} finally {
					orderLock.unlock();
				}
			}
		} catch (InterruptedException e) {
			log.error("order lock error", e);
		}

		return null;
	}
}
